﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

using ButtonBuilder.Plugin;

namespace ButtonBuilder.Controls
{
    public class Button : UserControl, IDisposable
    {
        public enum ResizeHandle
        {
            Top,
            Left,
            Bottom,
            Right,
            TopLeft,
            TopRight,
            BottomLeft,
            BottomRight,
            None
        }

        #region Events
        public event DraggableControlDragHandler Drag;
        public event DraggableControlResizeHandler Resize;
        public event PropertyChangedEventHandler PropertyChanged;
        public event Plugin.ActionHandler Click;
        #endregion

        #region Event Handlers
        public delegate void DraggableControlDragHandler(object sender, DraggableControlDragEventArgs e);
        public delegate void DraggableControlResizeHandler(object sender, DraggableControlResizeEventArgs e);
        #endregion

        #region Event Arguments

        /// <summary>
        /// Event arguments to be passed into the drag event.
        /// </summary>
        public class DraggableControlDragEventArgs : EventArgs
        {
            private Point _location;

            public DraggableControlDragEventArgs(Point location)
            {
                _location = location;
            }

            public int X
            {
                get
                {
                    return (int)_location.X;
                }
            }

            public int Y
            {
                get
                {
                    return (int)_location.Y;
                }
            }
        }

        /// <summary>
        /// Event arguments to be passed into the resize event.
        /// </summary>
        public class DraggableControlResizeEventArgs : EventArgs
        {
            private Size _size;

            public DraggableControlResizeEventArgs(Size size)
            {
                _size = size;
            }

            public int Width
            {
                get
                {
                    return (int)_size.Width;
                }
            }

            public int Height
            {
                get
                {
                    return (int)_size.Height;
                }
            }
        }
        #endregion

        public Button() : base()
        {
            Style style = new Style(typeof(TextBlock));
            style.Setters.Add(new Setter(TextBlock.BackgroundProperty, Brushes.Transparent));
            style.Setters.Add(new Setter(TextBlock.FontWeightProperty, FontWeights.Bold));
            style.Setters.Add(new Setter(TextBlock.FontStyleProperty, FontStyles.Normal));
            style.Setters.Add(new Setter(TextBlock.FontSizeProperty, 24d));
            Resources.Add("ButtonCaptionStyle", style);
            Resources.Add("ButtonCornerRadius", new CornerRadius(8d));

            ClickAction = new Plugin.EmptyAction();
        }

        #region Private Members
        private System.Windows.Threading.DispatcherTimer PressDownTimer { get; set; }

        private ResizeHandle ActiveHandle { get; set; }
        
        private Point PreviousLocation { get; set; }
        
        private Transform PreviousTransform { get; set; }

        protected bool EnterKeyDown { get; set; }
        #endregion

        #region Public Members
        public virtual Border Border { get { return null; } }

        public double CornerRadius
        {
            get
            {
                CornerRadius radius = (CornerRadius)Resources["ButtonCornerRadius"];
                return radius.TopLeft;
            }
            set
            {
                Resources["ButtonCornerRadius"] = new CornerRadius(value);
            }
        }

        public string Caption { get; set; }
        
        public IPlugin ClickAction { get; set; }

        public Page Page { get; set; }

        public double Left { get; set; }

        public double Top { get; set; }
        #endregion

        #region Protected Members
        protected ButtonCaption CaptionAdorner { get; set; }

        protected Data.ButtonFont Font { get; set; }

        protected bool MouseMoved { get; set; }

        protected bool MouseMoving { get; set; }

        protected MainWindow ParentWindow { get { return (MainWindow)Window.GetWindow(this); } }
        #endregion

        protected virtual void OnClick(Plugin.ActionEventArgs e)
        {
            Plugin.ActionHandler handler = Click;
            if (handler != null) handler(this, e);
        }

        private void OnDrag(DraggableControlDragEventArgs e)
        {
            DraggableControlDragHandler handler = Drag;
            if (handler != null) handler(this, e);

            OnPropertyChanged("Location");
        }

        private void OnResize(DraggableControlResizeEventArgs e)
        {
            DraggableControlResizeHandler handler = Resize;
            if (handler != null) handler(this, e);

            OnPropertyChanged("Size");
        }

        private void PressDownTimer_Tick(object sender, EventArgs e)
        {
            PressDownTimer.Stop();

            MouseMoving = false;
            MouseMoved = false;

            ApplyPressedTheme(false);

            if (!ParentWindow.EditMode)
                return;

            OnPressDownTimerTick();
        }

        private ResizeHandle GetActiveResizeHandle(Point currentLocation)
        {
            const int dragHandleWidth = 8;

            Window wnd = Window.GetWindow(this);

            double width = double.IsNaN(this.Width) ? this.ActualWidth : this.Width;
            double height = double.IsNaN(this.Height) ? this.ActualHeight : this.Height;

            var topHandle = new Rect(0, 0, width, dragHandleWidth);
            var leftHandle = new Rect(0, 0, dragHandleWidth, height);
            var bottomHandle = new Rect(0, height - dragHandleWidth, width - dragHandleWidth, dragHandleWidth);
            var rightHandle = new Rect(width - dragHandleWidth, 0, dragHandleWidth, height - dragHandleWidth);

            var topLeftHandle = new Rect(0, 0, dragHandleWidth, dragHandleWidth);
            var topRightHandle = new Rect(width - dragHandleWidth, 0, dragHandleWidth, dragHandleWidth);
            var bottomLeftHandle = new Rect(0, height - dragHandleWidth, dragHandleWidth, dragHandleWidth);
            var bottomRightHandle = new Rect(width - dragHandleWidth, height - dragHandleWidth, dragHandleWidth, dragHandleWidth);

            Point relativeLocation = wnd.TranslatePoint(currentLocation, this);

            if (topLeftHandle.Contains(relativeLocation))
            {
                return ResizeHandle.TopLeft;
            }
            else if (bottomRightHandle.Contains(relativeLocation))
            {
                return ResizeHandle.BottomRight;
            }
            else if (topRightHandle.Contains(relativeLocation))
            {
                return ResizeHandle.TopRight;
            }
            else if (bottomLeftHandle.Contains(relativeLocation))
            {
                return ResizeHandle.BottomLeft;
            }
            else if (topHandle.Contains(relativeLocation))
            {
                return ResizeHandle.Top;
            }
            else if (bottomHandle.Contains(relativeLocation))
            {
                return ResizeHandle.Bottom;
            }
            else if (leftHandle.Contains(relativeLocation))
            {
                return ResizeHandle.Left;
            }
            else if (rightHandle.Contains(relativeLocation))
            {
                return ResizeHandle.Right;
            }

            return ResizeHandle.None;
        }

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(name));
        }

        protected virtual void ApplyPressedTheme(bool pressed)
        {
            //Do nothing
        }

        protected virtual void OnPressDownTimerTick()
        {
            ContextMenu.PlacementTarget = this;
            ContextMenu.IsOpen = true;
        }
        
        protected override void OnMouseEnter(MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                this.CaptureMouse();
            }

            base.OnMouseEnter(e);
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            this.ReleaseMouseCapture();
            base.OnMouseLeave(e);
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            MouseMoved = false;

            this.CaptureMouse();
            base.OnMouseLeftButtonDown(e);

            if (!MouseMoving)
            {
                ApplyPressedTheme(true);

                if (e != null)
                {
                    if (PressDownTimer == null)
                    {
                        PressDownTimer = new System.Windows.Threading.DispatcherTimer();
                        PressDownTimer.Interval = TimeSpan.FromSeconds(0.5);
                        PressDownTimer.Tick += new EventHandler(PressDownTimer_Tick);
                    }

                    PressDownTimer.Start();
                }
            }
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            this.ReleaseMouseCapture();
            base.OnMouseLeftButtonUp(e);

            if (PressDownTimer != null)
                PressDownTimer.Stop();

            ApplyPressedTheme(false);

            if (!MouseMoved)
            {
                Plugin.ActionResult result = ClickAction.DoIt();
                OnClick(new Plugin.ActionEventArgs(result));
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (ContextMenu.IsVisible)
                return;

            if ((Parent as Canvas).ContextMenu.IsVisible)
            {
                this.Cursor = Cursors.Hand;
                return;
            }

            if (PressDownTimer != null)
                PressDownTimer.Stop();

            if (!ParentWindow.EditMode)
            {
                e.Handled = true;
                this.Cursor = Cursors.Hand;

                return;
            }

            MouseMoving = true;
                                              
            Window wnd = Window.GetWindow(this);
            Point currentLocation = e.MouseDevice.GetPosition(wnd);

            var move = new TranslateTransform(
                currentLocation.X - PreviousLocation.X, currentLocation.Y - PreviousLocation.Y);

            double width = double.IsNaN(this.Width) ? this.ActualWidth : this.Width;
            double height = double.IsNaN(this.Height) ? this.ActualHeight : this.Height;

            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (move.X != 0 || move.Y != 0)
                    MouseMoved = true;

                double xOffset = 0;
                double yOffset = 0;

                if (ActiveHandle == ResizeHandle.None)
                {
                    var group = new TransformGroup();
                    if (PreviousTransform != null)
                    {
                        group.Children.Add(PreviousTransform);
                    }
                    group.Children.Add(move);

                    this.RenderTransform = group;

                    // Invoke drag event
                    if (MouseMoved) OnDrag(new DraggableControlDragEventArgs(currentLocation));
                }
                else if (ActiveHandle == ResizeHandle.Top)
                {
                    if (height + PreviousLocation.Y - currentLocation.Y > this.MinHeight)
                    {
                        height = height + PreviousLocation.Y - currentLocation.Y;
                        yOffset = currentLocation.Y - PreviousLocation.Y;
                    }
                }
                else if (ActiveHandle == ResizeHandle.Left)
                {
                    if (width + PreviousLocation.X - currentLocation.X > this.MinWidth)
                    {
                        width = width + PreviousLocation.X - currentLocation.X;
                        xOffset = currentLocation.X - PreviousLocation.X;
                    }
                }
                else if (ActiveHandle == ResizeHandle.Bottom)
                {
                    if (height + currentLocation.Y - PreviousLocation.Y > this.MinHeight)
                    {
                        height = height + currentLocation.Y - PreviousLocation.Y;
                    }
                }
                else if (ActiveHandle == ResizeHandle.Right)
                {
                    if (width + currentLocation.X - PreviousLocation.X > this.MinWidth)
                    {
                        width = width + currentLocation.X - PreviousLocation.X;
                    }
                }
                else if (ActiveHandle == ResizeHandle.TopLeft)
                {
                    if (height + PreviousLocation.Y - currentLocation.Y > this.MinHeight)
                    {
                        height = height + PreviousLocation.Y - currentLocation.Y;
                        yOffset = currentLocation.Y - PreviousLocation.Y;
                    }

                    if (width + PreviousLocation.X - currentLocation.X > this.MinWidth)
                    {
                        width = width + PreviousLocation.X - currentLocation.X;
                        xOffset = currentLocation.X - PreviousLocation.X;
                    }
                }
                else if (ActiveHandle == ResizeHandle.TopRight)
                {
                    if (height + PreviousLocation.Y - currentLocation.Y > this.MinHeight)
                    {
                        height = height + PreviousLocation.Y - currentLocation.Y;
                        yOffset = currentLocation.Y - PreviousLocation.Y;
                    }

                    if (width + currentLocation.X - PreviousLocation.X > this.MinWidth)
                    {
                        width = width + currentLocation.X - PreviousLocation.X;
                    }
                }
                else if (ActiveHandle == ResizeHandle.BottomLeft)
                {
                    if (width + PreviousLocation.X - currentLocation.X > this.MinWidth)
                    {
                        width = width + PreviousLocation.X - currentLocation.X;
                        xOffset = currentLocation.X - PreviousLocation.X;
                    }

                    if (height + currentLocation.Y - PreviousLocation.Y > this.MinHeight)
                    {
                        height = height + currentLocation.Y - PreviousLocation.Y;
                    }
                }
                else if (ActiveHandle == ResizeHandle.BottomRight)
                {
                    if (width + currentLocation.X - PreviousLocation.X > this.MinWidth)
                    {
                        width = width + currentLocation.X - PreviousLocation.X;
                    }

                    if (height + currentLocation.Y - PreviousLocation.Y > this.MinHeight)
                    {
                        height = height + currentLocation.Y - PreviousLocation.Y;
                    }
                }

                // Update location and invoke resize event
                if (ActiveHandle != ResizeHandle.None && MouseMoved)
                {
                    this.Width = width;
                    this.Height = height;

                    Canvas.SetTop(this, Canvas.GetTop(this) + yOffset);
                    Canvas.SetLeft(this, Canvas.GetLeft(this) + xOffset);

                    OnResize(new DraggableControlResizeEventArgs(new Size(this.Width, this.Height)));
                }
            }
            else
            {
                ActiveHandle = GetActiveResizeHandle(currentLocation);

                switch (ActiveHandle)
                {
                    case ResizeHandle.TopLeft:
                    case ResizeHandle.BottomRight:
                        this.Cursor = Cursors.SizeNWSE;
                        break;
                    case ResizeHandle.TopRight:
                    case ResizeHandle.BottomLeft:
                        this.Cursor = Cursors.SizeNESW;
                        break;
                    case ResizeHandle.Top:
                    case ResizeHandle.Bottom:
                        this.Cursor = Cursors.SizeNS;
                        break;
                    case ResizeHandle.Left:
                    case ResizeHandle.Right:
                        this.Cursor = Cursors.SizeWE;
                        break;
                    case ResizeHandle.None:
                        this.Cursor = Cursors.Hand;
                        break;
                }
            }

            PreviousLocation = currentLocation;
            PreviousTransform = this.RenderTransform;

            base.OnMouseMove(e);

            MouseMoving = false;
        }

        public virtual Data.Button GetData()
        {
            return new Data.Button(this.GetType())
            {
                Top = this.Top,
                Left = this.Left,
                Width = this.ActualWidth,
                Height = this.ActualHeight,
                TabIndex = this.TabIndex,
                ZIndex = Canvas.GetZIndex(this),
                ClickAction = (Plugin.Action)this.ClickAction,
                Caption = this.Caption,
                Font = this.Font
            };
        }

        public virtual void SetData(Data.Button data)
        {
            Width = data.Width;
            Height = data.Height;
            Top = data.Top;
            Left = data.Left;
            TabIndex = data.TabIndex;
            ClickAction = data.ClickAction;
            Caption = data.Caption;
            Font = data.Font;
        }
        
        protected class ButtonCaption : Adorner
        {
            private Button _button;
            private TextBlock _control;
            private Point _location;
            private ArrayList _logicalChildren;

            public ButtonCaption(Button button, UIElement target, string caption)
                : base(target)
            {
                _button = button;

                _control = new TextBlock();
                _control.Text = caption;
                _control.Cursor = Cursors.Hand;
                _control.Style = (Style)_button.Resources["ButtonCaptionStyle"];

                _control.MouseMove += new MouseEventHandler(OnCaptionMouseMoveEvent);
                _control.MouseLeftButtonDown += new MouseButtonEventHandler(OnCaptionMouseClickEvent);
                _control.MouseLeftButtonUp += new MouseButtonEventHandler(OnCaptionMouseClickEvent);

                ContextMenu = _button.ContextMenu;

                base.AddLogicalChild(_control);
                base.AddVisualChild(_control);
            }

            private void OnCaptionMouseMoveEvent(object sender, MouseEventArgs e)
            {
                AdornedElement.RaiseEvent(e);
            }

            private void OnCaptionMouseClickEvent(object sender, MouseButtonEventArgs e)
            {
                AdornedElement.RaiseEvent(e);
            }

            public string Text
            {
                get { return _control.Text; }
                set { _control.Text = value; }
            }

            public TextBlock CaptionControl
            {
                get { return _control; }
            }

            #region Measure/Arrange

            /// <summary>
            /// Allows the control to determine how big it wants to be.
            /// </summary>
            /// <param name="constraint">A limiting size for the control.</param>
            protected override Size MeasureOverride(Size constraint)
            {
                _control.Measure(constraint);
                return _control.DesiredSize;
            }

            /// <summary>
            /// Positions and sizes the control.
            /// </summary>
            /// <param name="finalSize">The actual size of the control.</param>		
            protected override Size ArrangeOverride(Size finalSize)
            {
                _location = new Point()
                {
                    X = (_button.Width - _control.ActualWidth) / 2,
                    Y = (_button.Height - _control.ActualHeight) / 2
                };

                _control.Arrange(new Rect(_location, finalSize));

                return finalSize;
            }

            #endregion // Measure/Arrange

            #region Visual Children

            /// <summary>
            /// Required for the element to be rendered.
            /// </summary>
            protected override int VisualChildrenCount
            {
                get { return 1; }
            }

            /// <summary>
            /// Required for the element to be rendered.
            /// </summary>
            protected override Visual GetVisualChild(int index)
            {
                if (index != 0)
                    throw new ArgumentOutOfRangeException("index");

                return _control;
            }
            #endregion // Visual Children

            #region Logical Children

            /// <summary>
            /// Required for the displayed element to inherit property values
            /// from the logical tree, such as FontSize.
            /// </summary>
            protected override IEnumerator LogicalChildren
            {
                get
                {
                    if (_logicalChildren == null)
                    {
                        _logicalChildren = new ArrayList();
                        _logicalChildren.Add(_control);
                    }

                    return _logicalChildren.GetEnumerator();
                }
            }

            #endregion // Logical Children
        }

        #region IDisposable Members

        public virtual void Dispose()
        {
            
        }

        #endregion
    }
}
